#include "fh_spi_common.h"

static void Spi_EccEnable(struct fh_spi *dw, u32 enable)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg0);
	data = enable ? (data | 0x1) : (data & ~0x1);
	dw_writel(dw, ecc_reg0, data);
}

static void Spi_SetEccStepSize(struct fh_spi *dw, enum spi_ecc_data_per_step step)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg0);
	data &= ~ECC_DATA_STEP_RANGE;
	data |= step;
	dw_writel(dw, ecc_reg0, data);
}

static void Spi_SetEccStrength(struct fh_spi *dw, enum spi_ecc_strength strength)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg0);
	data &= ~ECC_STRENGTH_RANGE;
	data |= strength;
	dw_writel(dw, ecc_reg0, data);
}

static void Spi_SetEccTransferMode(struct fh_spi *dw, u32 mode)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg0);
	data = mode ? (data | (0x1 << 5)) : (data & ~(0x1 << 5));
	dw_writel(dw, ecc_reg0, data);
}

static void Spi_SetSpareSize(struct fh_spi *dw, u32 size)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg0);
	data &= ~ECC_SPARE_LENGTG_RANGE;
	data |= (size << ECC_SPARE_LENGTH_SHIFT) & ECC_SPARE_LENGTG_RANGE;
	dw_writel(dw, ecc_reg0, data);
}

static void Spi_SetEccPostion(struct fh_spi *dw, u32 pos)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg1);
	data &= ~ECC_START_POS_RANGE;
	data |= (pos & ECC_START_POS_RANGE);
	dw_writel(dw, ecc_reg1, data);
}

static void Spi_SetPageDataSize(struct fh_spi *dw, enum spi_page_data_size size)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg1);
	data &= ~ECC_PAGE_DATASIZE_RANGE;
	data |= size;
	dw_writel(dw, ecc_reg1, data);
}

static void Spi_SetPageSpageSize(struct fh_spi *dw, enum spi_page_spare_size size)
{
	u32 data = 0;

	data = dw_readl(dw, ecc_reg1);
	data &= ~ECC_PAGE_SPARESIZE_RANGE;
	data |= (size << ECC_PAGE_SPARESIZE_SHIFT) & ECC_PAGE_SPARESIZE_RANGE;
	dw_writel(dw, ecc_reg1, data);
}

static u32 Spi_GetEccStatus(struct fh_spi *dw)
{
	return dw_readl(dw, ecc_status);
}

static u32 Spi_GetEccInfo(struct fh_spi *dw)
{
	return dw_readl(dw, nfc_ecc_info0);
}

static int Spi_SetDmaInc(struct fh_spi *dw, unsigned int width)
{
	u32 data;

	data = dw_readl(dw, ccfgr);
	data &= ~(0x3 << 19);
	switch (width) {
		case 32:
#if defined(CONFIG_ARCH_FH885xV300)
			data |= 0x1 << 19;
#else
			data |= 0x3 << 19;
#endif
			break;
		case 16:
			data |= 0x2 << 19;
			break;
		case 8:
#if defined(CONFIG_ARCH_FH885xV300)
			data |= 0x3 << 19;
#else
			data |= 0x1 << 19;
#endif
			break;
		case 0:
			data |= 0x0 << 19;
			break;
	}
	dw_writel(dw, ccfgr, data);
	return 0;
}

int fh_spi_enable_ecc(struct fh_spi_controller *fh_spi)
{
	Spi_Enable(&fh_spi->dwc, SPI_DISABLE);
	Spi_EccEnable(&fh_spi->dwc, 1);
	Spi_Enable(&fh_spi->dwc, SPI_ENABLE);

	return 0;
}

int fh_spi_disable_ecc(struct fh_spi_controller *fh_spi)
{
	Spi_Enable(&fh_spi->dwc, SPI_DISABLE);
	Spi_EccEnable(&fh_spi->dwc, 0);
	Spi_Enable(&fh_spi->dwc, SPI_ENABLE);

	return 0;
}

static int fh_spi_set_ecc_mode(struct fh_spi_controller *fh_spi, u32 mode)
{
	Spi_Enable(&fh_spi->dwc, SPI_DISABLE);
	Spi_SetEccTransferMode(&fh_spi->dwc, mode);
	Spi_Enable(&fh_spi->dwc, SPI_ENABLE);

	return 0;
}

static void fh_spi_set_dma_configuration(struct fh_spi_controller *fh_spi, u32 width)
{
	Spi_Enable(&fh_spi->dwc, SPI_DISABLE);
	Spi_SetDmaInc(&fh_spi->dwc, width);
	Spi_Enable(&fh_spi->dwc, SPI_ENABLE);
}

static void fh_spi_wait_ecc_complete(struct fh_spi_controller *fh_spi)
{
	int data;

	do
	{
		data = Spi_GetEccStatus(&fh_spi->dwc);
	} while (!(data & 0x1));
}


void fh_spi_config_ecc(struct spi_master *master)
{
	struct fh_spi_controller *fh_spi = spi_master_get_devdata(master);
	struct spi_nand_ecc_ctrl ecc = master->ecc;

	Spi_Enable(&fh_spi->dwc, SPI_DISABLE);
	Spi_SetSpareSize(&fh_spi->dwc, 0);
	Spi_SetEccStepSize(&fh_spi->dwc, ECC_PER_1K_BYTE);
	Spi_SetEccStrength(&fh_spi->dwc, ECC_8BIT_MODE);
	Spi_SetPageSpageSize(&fh_spi->dwc, ecc.total);
	Spi_SetPageDataSize(&fh_spi->dwc, (ecc.size == 0x1000) ? ECC_PAGE_DATASIZE_4K : ECC_PAGE_DATASIZE_2K);
	Spi_SetEccPostion(&fh_spi->dwc, (ecc.total / 2));
	Spi_Enable(&fh_spi->dwc, SPI_ENABLE);
}

void fh_spi_get_ecc_status(struct spi_master *master,
unsigned int status,
unsigned int *corrected,
unsigned int *ecc_errors)
{
	unsigned int mask = 0x1F;
	unsigned int data = 0;
	unsigned int ecc_status = 0;
	unsigned int err_status = 0;
	struct fh_spi_controller *fh_spi = spi_master_get_devdata(master);

	data = Spi_GetEccStatus(&fh_spi->dwc);
	err_status = Spi_GetEccInfo(&fh_spi->dwc);
	ecc_status = (data & ECC_STATUS_RANGE) >> ECC_STATUS_SHIFT;
	*corrected = (err_status & 0xa) ? (0): (ecc_status & mask);
	*ecc_errors = (err_status & 0xa) ? (1) : (0);
}

void fh_spi_internal_ecc_init(struct spi_master *master)
{
	struct fh_spi_controller *fh_spi;
	struct spi_nand_ecc_ctrl *ecc = &master->ecc;

	fh_spi = spi_master_get_devdata(master);
	fh_spi->dwc.ecc.enable_ecc  = fh_spi_enable_ecc;
	fh_spi->dwc.ecc.disable_ecc = fh_spi_disable_ecc;
	fh_spi->dwc.ecc.set_mode    = fh_spi_set_ecc_mode;
	fh_spi->dwc.ecc.config_dma  = fh_spi_set_dma_configuration;
	fh_spi->dwc.ecc.complete    = fh_spi_wait_ecc_complete;

	ecc->engine_type    = NAND_ECC_ENGINE_TYPE_ON_HOST;
	ecc->ecc_config     = fh_spi_config_ecc;
	ecc->get_ecc_status = fh_spi_get_ecc_status;
}
